package com.nulldev.util.security.encryptAPI_rw.impls.ice_thirdparty;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;

import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.security.encryptAPI_rw.api.ICipherType;
import com.nulldev.util.security.encryptAPI_rw.api.IStandardBlockingCipher;
import com.nulldev.util.security.encryptAPI_rw.api.IStandardStreamingCipher;
import com.nulldev.util.security.encryptAPI_rw.impls.ice_thirdparty.IceKey.Levels;

public class ICECipher implements IStandardBlockingCipher, IStandardStreamingCipher {

	private static final String[] CIPHER_NAMES =
		{ "Ice", "ICE" };

	private final Levels level;

	public ICECipher() {
		this(Levels.LEVEL_8);
	}

	public ICECipher(final Levels level) {
		this.level = Variables.requireNonNullObject(level, "level");
	}

	@Override
	public String getEncryptorID() {
		return "ecr_v3.thirdparty.ice";
	}

	@Override
	public String[] getCipherNames() {
		return CIPHER_NAMES;
	}

	private static void __pad_array(final byte[] arr, final int start, final byte c) {
		for (int i = start; i < arr.length; i++) {
			arr[i] = c;
		}
	}

	@Override
	public byte[] encrypt(final byte[] data, final byte[] key) {
		final IceKey ice = new IceKey(this.level.value());
		ice.set(Variables.requireNonNullObject(key, "key"));
		final int len = MathUtil.padToInterval(Variables.requireNonNullObject(data, "data").length, ice.blockSize());
		final byte[] cipher_text = new byte[len];
		System.arraycopy(data, 0, cipher_text, 0, data.length);
		final int diff = len - data.length;
		if (diff != 0) {
			throw new UnsupportedOperationException("data array length must be interval of " + ice.blockSize() + "!");
		}
		for (int i = 0; i < cipher_text.length; i += 8) {
			final byte[] buf = Arrays.copyOfRange(cipher_text, i, i + 8);
			ice.encrypt(buf, buf);
			System.arraycopy(buf, 0, cipher_text, i, 8);
		}
		return cipher_text;
	}

	@Override
	public byte[] decrypt(final byte[] data, final byte[] key) {
		final IceKey ice = new IceKey(this.level.value());
		ice.set(Variables.requireNonNullObject(key, "key"));
		final byte[] raw_text = new byte[data.length];
		for (int i = 0; i < raw_text.length; i += 8) {
			final byte[] buf = Arrays.copyOfRange(data, i, i + 8);
			ice.decrypt(buf, buf);
			System.arraycopy(buf, 0, raw_text, i, 8);
		}
		return raw_text;
	}

	@Override
	public long encrypt(final InputStream is, final OutputStream os, final byte[] privateKey) {
		Variables.requireNonNull(is, "is");
		Variables.requireNonNull(os, "os");
		Variables.requireNonNull(privateKey, "privateKey");
		long count = 0;
		final IceKey ice = new IceKey(this.level.value());
		ice.set(privateKey);
		final int BLOCK_SIZE = ice.blockSize();
		final byte[] b = new byte[BLOCK_SIZE];
		try {
			while ((count += (is.read(b, 0, BLOCK_SIZE))) != -1) {
				ice.encrypt(b, b);
				os.write(b, 0, BLOCK_SIZE);
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		return count;
	}

	@Override
	public long decrypt(final InputStream is, final OutputStream os, final byte[] privateKey) {
		Variables.requireNonNull(is, "is");
		Variables.requireNonNull(os, "os");
		Variables.requireNonNull(privateKey, "privateKey");
		long count = 0;
		final IceKey ice = new IceKey(this.level.value());
		ice.set(privateKey);
		final int BLOCK_SIZE = ice.blockSize();
		final byte[] b = new byte[BLOCK_SIZE];
		try {
			while ((count += (is.read(b, 0, BLOCK_SIZE))) != -1) {
				ice.decrypt(b, b);
				os.write(b, 0, BLOCK_SIZE);
			}
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
		return count;
	}

	@Override
	public ICipherType getCipherType() {
		return ICipherType.PRIVATE_KEY;
	}

}
